from typing import List, MutableSet, Optional, Union

from common.logger import logger
from common.geometry.geom import Geom
from common.geoimage.scene_meta import SceneMeta

from catalog.models.mgrs import MGRSRecord
from catalog.models.place import PlaceRecord


class PlaceAPI(object):
    """A place service backed by PAII catalog service"""

    def __init__(self):
        pass

    @staticmethod
    def get_geom_list_with_place_name(
        target_name: Optional[Union[str, List[str]]] = None,
        target_name_exact_mode: bool = False,
        target_parent_name: Optional[str] = None,
        target_parent_name_exact_mode: bool = False,
        region_level: Optional[Union[int, List[int]]] = None,
        name_exact_mode: bool = False,
        name_cn: Union[str, List[str], None] = None,
        name_en: Union[str, List[str], None] = None,
        parent_name_cn: Optional[str] = None,
        parent_name_en: Optional[str] = None,
        year: Union[int, str, None] = "latest",
    ) -> List[Geom]:
        """
        Get the geom list of given place conditions.

        Parameters
        ----------
        target_name : Union[str, List[str]], optional
            Name of AOI, all areas contains target_name will be returned unless
            target_name_exact_mode is True, by default None

        target_name_exact_mode : bool, optional
            Whether to use the exact mode in target location name.
            If True, name must be an exact match, otherwise,
            if the given name is within the field's value then it is a match.

        target_parent_name : str, optional
            Name of AOI parent, all areas' parent name contains target_parent_name
            will be returned unless target_parent_name_exact_mode is True
            For example, California is the parent of Santa Clara, United States
            is the parent of both California and Santa Clara. 辽宁省 is the parent of 沈阳市,
            中华人民共和国 is the parent of both 辽宁省 and 沈阳市

        target_parent_name_exact_mode: bool, optional
            Whether to use the exact mode in target parent location name.
            If True, name must be an exact match, otherwise,
            if the given name is within the field's value then it is a match.

        region_level : Union[int, List[int]]
            The region level of the AOI:
            0: Country,
            1: Province / state,
            2: Prefecture (Only for area in China)
            3: County

        name_cn : str
            Deprecated, use target_name instead
            Name of AOI in Chinese, all areas contains name_cn will be returned.

        name_en : str
            Deprecated, use target_name instead
            Name of AOI in English, all areas contains name_en will be returned.

        parent_name_cn : str
            Deprecated, use target_parent_name instead
            Name of AOI parent in Chinese, all areas contains name_en will be returned.
            For example, 辽宁省 is the parent of 沈阳市, 中华人民共和国 is the parent of both
            辽宁省 and 沈阳市

        parent_name_en : str
            Name of AOI parent in Chinese, all areas contains name_en will be returned.
            For example, California is the parent of Santa Clara, United States
            is the parent of both California and Santa Clara

        name_exact_mode : bool
            Whether to use the exact mode in specifying location name.
            If True, name must be an exact match, otherwise,
            if the given name is within the field's value then it is a match.
            This mode is turned on or off for the following querying fields:
            `name_cn`, `name_en`, `parent_name_en` and `parent_name_cn`.

        year : str, optional
            Which year of data should be selected. Currently, for China dataset, we have year from 2014 to 2022.
            Source from shengshixian.com
            For United States dataset, we have year from 2018 to 2021, source from census.gov.
            If set to int or a digit string, the data of selected year will be returned.
            If set to None, the dataset with all years will be returned.
            If set to "latest", only the data for latest year will be returned. for example,
            areas in China of year 2022 will be returned and areas in USA of year 2021 will be returned.

        Returns
        -------
        List[geom]
            List of Geom instances.
        """
        local_args = locals()
        condition_str = ",".join([k + ":" + str(v) for k, v in local_args.items()])

        if target_name is None and (name_cn is not None or name_en is not None):
            logger.warning(
                "Deprecated: name_cn or name_en will be deprecated, use target_name instead."
            )
            target_name = name_cn if name_cn is not None else name_en

        if target_parent_name is None and (
            parent_name_cn is not None or parent_name_en is not None
        ):
            logger.warning(
                "Deprecated: parent_name_cn or parent_name_en will be deprecated,\
                use target_parent_name instead."
            )
            target_name = name_cn if name_cn is not None else name_en

        if name_exact_mode:
            logger.warning(
                "Deprecated: name_exact_mode will be deprecated, use target_name_exact_mode\
                and target_parent_name_exact_mode instead."
            )
            target_parent_name_exact_mode = True
            target_name_exact_mode = True

        items = PlaceRecord.query_many_items(
            target_name=target_name,
            target_name_exact_mode=target_name_exact_mode,
            region_level=region_level,
            target_parent_name=target_parent_name,
            target_parent_name_exact_mode=target_parent_name_exact_mode,
            year=year,
        )

        result_list = []
        query_extend_name = []
        item: PlaceRecord = None
        for item in items:
            result_list.append(item.geom)
            query_extend_name.append(item.parent_name_en)

        if len(result_list) == 0:
            logger.warning("No place found with condition:\n{}\n".format(condition_str))
        elif len(result_list) > 1:
            logger.warning(
                "More than one place found with condition: {}\n{}\n".format(
                    condition_str, "\n".join(sorted(query_extend_name))
                )
            )

        return result_list

    @staticmethod
    def get_mgrs_set_with_place_name(
        target_name: Optional[Union[str, List[str]]] = None,
        target_name_exact_mode: bool = False,
        target_parent_name: Optional[str] = None,
        target_parent_name_exact_mode: bool = False,
        region_level: Optional[Union[int, List[int]]] = None,
        name_exact_mode: bool = False,
        name_cn: Union[str, List[str], None] = None,
        name_en: Union[str, List[str], None] = None,
        parent_name_cn: Optional[str] = None,
        parent_name_en: Optional[str] = None,
        year: Union[int, str, None] = "latest",
    ) -> MutableSet[str]:
        """
        Get the geom list of given place conditions.

        Parameters
        ----------
        target_name : Union[str, List[str]], optional
            Name of AOI, all areas contains target_name will be returned unless
            target_name_exact_mode is True, by default None

        target_name_exact_mode : bool, optional
            Whether to use the exact mode in target location name.
            If True, name must be an exact match, otherwise,
            if the given name is within the field's value then it is a match.

        target_parent_name : str, optional
            Name of AOI parent, all areas' parent name contains target_parent_name
            will be returned unless target_parent_name_exact_mode is True
            For example, California is the parent of Santa Clara, United States
            is the parent of both California and Santa Clara. 辽宁省 is the parent of 沈阳市,
            中华人民共和国 is the parent of both 辽宁省 and 沈阳市

        target_parent_name_exact_mode: bool, optional
            Whether to use the exact mode in target parent location name.
            If True, name must be an exact match, otherwise,
            if the given name is within the field's value then it is a match.

        region_level : int
            The region level of the AOI:
            0: Country,
            1: Province / state,
            2: Prefecture (Only for area in China)
            3: County

        name_cn : str
            Deprecated, use target_name instead
            Name of AOI in Chinese, all areas contains name_cn will be returned.

        name_en : str
            Deprecated, use target_name instead
            Name of AOI in English, all areas contains name_en will be returned.

        parent_name_cn : str
            Deprecated, use target_parent_name instead
            Name of AOI parent in Chinese, all areas contains name_en will be returned.
            For example, 辽宁省 is the parent of 沈阳市, 中华人民共和国 is the parent of both
            辽宁省 and 沈阳市

        parent_name_en : str
            Name of AOI parent in Chinese, all areas contains name_en will be returned.
            For example, California is the parent of Santa Clara, United States
            is the parent of both California and Santa Clara

        name_exact_mode : bool
            Whether to use the exact mode in specifying location name.
            If True, name must be an exact match, otherwise,
            if the given name is within the field's value then it is a match.
            This mode is turned on or off for the following querying fields:
            `name_cn`, `name_en`, `parent_name_en` and `parent_name_cn`.

        year : str, optional
            Which year of data should be selected. Currently, for China dataset, we have year from 2014 to 2022.
            Source from shengshixian.com
            For United States dataset, we have year from 2018 to 2021, source from census.gov.
            If set to int or a digit string, the data of selected year will be returned.
            If set to None, the dataset with all years will be returned.
            If set to "latest", only the data for latest year will be returned. for example,
            areas in China of year 2022 will be returned and areas in USA of year 2021 will be returned.

        Returns
        -------
        MutableSet[str]
            MutableSet of strings.
        """
        mgrs_set = set()
        geom_list = PlaceAPI.get_geom_list_with_place_name(
            target_name=target_name,
            target_name_exact_mode=target_name_exact_mode,
            target_parent_name=target_parent_name,
            target_parent_name_exact_mode=target_parent_name_exact_mode,
            region_level=region_level,
            name_exact_mode=name_exact_mode,
            name_cn=name_cn,
            name_en=name_en,
            parent_name_cn=parent_name_cn,
            parent_name_en=parent_name_en,
            year=year,
        )

        for geom in geom_list:
            result_list: List[MGRSRecord] = MGRSRecord.query_many_items(spatial=geom)
            for item in result_list:
                mgrs_set.add(item.uid)

        return mgrs_set

    @staticmethod
    def get_mgrs_set_by_geom(spatial: Geom) -> MutableSet[str]:
        """Get set of MGRS tile intersecting with a given `Geom` as ids

        Parameters
        ----------
        spatial : Geom
            A given `Geom` to query.

        Returns
        -------
        MutableSet[str]
            A set of MGRS tile ids for tiles intersecting with the given `Geom`.
        """
        query = MGRSRecord.query_many_items(spatial=spatial)
        mgrs_tileid_set = set()
        for item in query:
            mgrs_tileid_set.add(item.uid)
        return mgrs_tileid_set

    @staticmethod
    def get_geom_with_mgrs_tile(mgrs_tile: str) -> Optional[Geom]:
        item = MGRSRecord.get_one_item(mgrs_tile)
        if item is not None:
            return item.geom
        else:
            raise ValueError("No MGRS tile of {} found in database".format(mgrs_tile))

    @staticmethod
    def get_geom_list_with_mgrs_tiles(mgrs_tiles: List[str]) -> List[Optional[Geom]]:
        res = []
        for item in MGRSRecord.get_many_items(mgrs_tiles):
            res.append(item.geom)
        return res

    @staticmethod
    def get_meta_with_mgrs_tiles(mgrs_tile: str, resolution=10, n_bands=1) -> SceneMeta:
        item: MGRSRecord = MGRSRecord.get_one_item(mgrs_tile)
        meta = item.get_scenemeta(resolution=resolution, n_bands=n_bands)
        return meta


if __name__ == "__main__":
    import multiprocessing as mp

    area_name = "山东省"
    mgrs_set_shandong = PlaceAPI.get_mgrs_set_with_place_name(name_cn=area_name)
    mgrs_set_henan = PlaceAPI.get_mgrs_set_with_place_name(name_cn="河南省")
    print("new args")
    mgrs_set_henan = PlaceAPI.get_mgrs_set_with_place_name(target_name="河南省")

    mgrs_set = set.union(mgrs_set_henan, mgrs_set_shandong)
    print(mgrs_set)
    print(len(mgrs_set))

    test_mgrs_item = PlaceRecord.query_many_items(target_name="beijing")[0]
    mgrs_geom = test_mgrs_item.geom

    mgrs_set = PlaceAPI.get_mgrs_set_by_geom(mgrs_geom)
    print(mgrs_set)
    print(len(mgrs_set))

    def parallel_test_mgrs_set_with_place_name(area_name):
        PlaceAPI.get_mgrs_set_with_place_name(target_name=area_name)

    def parallel_test_geom_list_with_place_name(area_name):
        PlaceAPI.get_geom_list_with_place_name(target_name=area_name)

    def parallel_test_mgrs_set_by_geom(aoi_geom):
        PlaceAPI.get_mgrs_set_by_geom(aoi_geom)

    def parallel_test_mgrs_get_by_id(tileid):
        MGRSRecord.get_one_item(tileid)

    repeat_time = 10000
    pool = mp.Pool(200)

    for _ in range(repeat_time):
        pool.apply_async(
            parallel_test_mgrs_set_with_place_name,
            args=[area_name],
            error_callback=print,
        )

    for _ in range(repeat_time):
        pool.apply_async(
            parallel_test_mgrs_get_by_id,
            args=["48RVV"],
            error_callback=print,
        )
    for _ in range(repeat_time):
        pool.apply_async(
            parallel_test_geom_list_with_place_name,
            args=[area_name],
            error_callback=print,
        )
    for _ in range(repeat_time):
        pool.apply_async(
            parallel_test_mgrs_set_by_geom, args=[mgrs_geom], error_callback=print
        )

    pool.close()
    pool.join()
    print("all done")
